<?php
/**
 * @copyright Aileenguan
 * @version 
 */

error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);

class VideoConvertTask
{
	protected $config;
	protected $dbLink;
	protected $currentThreadsNum;
	protected $currentMissions;
	protected $streams;
	private $date;
	private $handle;
	
	/**
	 * 存储服务器信息
	 *
	 * @var array
	 */
	private $server;

	/**
     * 任务初始化函数
     *
     * @param string $configFile
     */
	function __construct($configFile)
	{
		//检测环境
		if(!class_exists('ffmpeg_movie'))
		{
			die('php-ffmpeg扩展不存在,无法执行转码任务');
		}

		//读取配置信息
		$this->config = parse_ini_file($configFile,true);
		$this->printline('转码服务开始');
		$this->printline('读取配置文件'.$configFile);
		
		if (!is_dir($this->config['PATH']['log'])) 
		{
			$this->createDir($this->config['PATH']['log']);
		}
		if (!is_dir($this->config['PATH']['tmp'])) //检测临时目录
		{
			$this->createDir($this->config['PATH']['tmp']);
		}
		//初始化数据连结
		$this->connectDatabase();
		
		//初始化存储点信息
		$this->getServerInfo();

		//重置任务状态
		$this->query("UPDATE cms_resource SET videostate = '0' WHERE videostate = '2'",false);
		//$this->query("UPDATE qu_video_convert SET flag = '0' WHERE flag = '2'", false);

		//初始化参数
		$this->currentThreadsNum = 0;
		$this->currentMissions = array();
		$this->streams = array();
	}
	
	/**
	 * 更新服务器信息
	 *
	 */
	private function getServerInfo()
	{
		$this->printline('获取存储点配置信息....');
		sleep(2);
		$this->server = array(); //reset the server configure
		$storage = $this->query("SELECT * FROM cms_server");
		foreach ($storage as $s)
		{
			$this->printline("读取到存储点(" . $s['serverid'] . "):" . $s['servername']);
			
			if (empty($s['serverpath2'])) 
			{
				$this->printline("没有设置访问路径");
				$this->printline('等待10秒后重试', false);
				sleep(10);
				$this->getServerInfo();				
			}
			elseif (!is_dir($s['serverpath2'])) 
			{
				$this->printline("路径不存在，无法正常进行：" . $s['serverpath2'], true);
				$this->printline('等待10秒后重试', false);
				sleep(10);
				$this->getServerInfo();
			}
			$this->server[$s['serverid']] = $s['serverpath2'];
		}
	}

	protected function createDir($path)
	{
		$dirs = explode('/', $path);
		$dirpath = '';
		while ($directory = array_shift($dirs)) {
			$dirpath .='/'.$directory;
			echo $dirpath;
			if(!is_dir($dirpath))
			{
				mkdir($dirpath);
				if(!chmod($dirpath, 0777))
				{
					exit('无法创建目录' . $path . "\n程序被迫中止");					
				}
			}
		}
	}

	protected function printline($str, $log = true)
	{
		$line = date('y-m-d H:i:s').' '.$str."\r\n";
		if (!is_dir($this->config['PATH']['log']))
		{
			exit('日志目录不存在:' . $this->config['PATH']['log'] . "  程序被迫中止。\n");
		}

		if($log)
		{
			if(empty($this->date) || $this->date != date('Ymd'))
			{
				$this->date = date('Ymd');

				if(!empty($this->handle))
				{
					fclose($this->handle);
				}

				$this->handle = fopen($this->config['PATH']['log'] . '/' . $this->date . '.log', 'ab');
			}

			if(empty($this->handle))
			{
				echo "无法创建日志文件\r\n";
			}
			else
			{
				if(false === fwrite($this->handle, $line))
				{
					echo "无法写入日志文件\r\n";
				}
			}
		}
		echo $line;
	}

	private function getInsertID()
	{
		return uniqid($this->config['DATABASE']['webguidname']);
	}

	/**
     * 数据库连接方法
     *
     */
	private function connectDatabase()
	{
		$this->printline('连接数据库'.$this->config['DATABASE']['host'].':'.$this->config['DATABASE']['port']);

		$this->dbLink =
		mysql_pconnect($this->config['DATABASE']['host'].':'.$this->config['DATABASE']['port'],
		$this->config['DATABASE']['user'],
		$this->config['DATABASE']['password']) or die(mysql_error());

		$this->printline('选择数据库'.$this->config['DATABASE']['database']);
		mysql_query("SET NAMES utf8");
		mysql_select_db($this->config['DATABASE']['database'],$this->dbLink) or die(mysql_error());
	}

	/**
     * 数据库查询方法
     *
     * @param string $query
     * @return array
     */
	protected function query($query,$return = true)
	{
		//测试连接是否存在
		while(!@mysql_ping($this->dbLink))
		{
			$this->printline('数据库连接中断');
			sleep($this->config['DATABASE']['try_time']);

			@mysql_close($this->dbLink);

			$this->printline('尝试新建连接'.$this->config['DATABASE']['host'].':'.$this->config['DATABASE']['port']);
			$this->dbLink =
			mysql_pconnect($this->config['DATABASE']['host'].':'.$this->config['DATABASE']['port'],
			$this->config['DATABASE']['user'],
			$this->config['DATABASE']['password']);

			if(!$this->dbLink)
			{
				continue;
			}
			else
			{
				if(!mysql_select_db($this->config['DATABASE']['database'],$this->dbLink))
				{
					continue;
				}
				else
				{
					break;
				}
			}
		}

		//开始查询
		$result = mysql_query($query);

		if(!$result)
		{
			$this->printline(mysql_error());
		}

		$rows = array();

		if($result && $return)
		{
			while($row = mysql_fetch_assoc($result))
			{
				$rows[] = $row;
			}

			//释放内存
			unset($row);
			unset($result);
			unset($query);

			return $rows;
		}
		else
		{
			return false;
		}
	}

	protected function getVideoInfo($path)
	{
		$info = array();
		$movie = new ffmpeg_movie( $path, false);

		if(is_object($movie))
		{
			//获取视频编码
			$codec = $movie->getVideoCodec();
			$info['codec'] = $codec ? $codec : 'unknown';

			//获取时长
			$info['duration'] = $movie->getDuration();
		}
		else
		{
			$info['codec'] = 'unknown';
			$info['duration'] = NULL;
		}

		unset($movie);
		return $info;
	}

	protected function resizeImage($sourceFileName, $destStruct)
	{
		if(file_exists($sourceFileName))
		{
			list($width, $height) = getimagesize($sourceFileName);

			foreach($destStruct as $val)
			{
				if(0 == $val['width'] && 0 == $val['height'])
				{
					copy($sourceFileName,$val['file']);
				}
				else
				{
					$thumb = imagecreatetruecolor($val['width'], $val['height']);
					$source = imagecreatefromjpeg($sourceFileName);
					imagecopyresized($thumb, $source, 0, 0, 0, 0, $val['width'], $val['height'], $width, $height);
					imagejpeg($thumb,$val['file']);
					imagedestroy($thumb);
					imagedestroy($source);
				}
			}

			unlink($sourceFileName);
		}
		else
		{
			$this->printline('图片不存在:'.$sourceFileName);
		}
	}

	/**
     * 获取任务
     * flag
     * 0->等待转码
     * 1->转码成功
     * 2->正在转码
     * 3->转码失败
     * 4->转码超时
     *
     * @return array
     */
	protected function getMissions()
	{
		$this->printline('', false);
		$this->printline('获取新任务.....', false);
		$rs = $this->query("SELECT COUNT(*) AS total FROM cms_resource WHERE ifvideo=1 AND (
        videostate = 0 OR 
        (videostate = 3 AND retry < " . $this->config['CONVERT']['try_time'] . ") OR
        (videostate = 4 AND retry < " . $this->config['CONVERT']['try_time'] . ")
        )");
		$total = $rs[0]['total'];
		$this->printline('队列中共有' . $total . '条任务', false);

		$missions = $this->query("SELECT rid,filepath,serverid FROM cms_resource WHERE ifvideo=1 AND (
        videostate = 0 OR 
        (videostate = 3 AND retry < " . $this->config['CONVERT']['try_time'] . ") OR
        (videostate = 4 AND retry < " . $this->config['CONVERT']['try_time'] . ")
        ) LIMIT " .($this->config['CONVERT']['max_thread'] - $this->currentThreadsNum));

		//$rootPath = '/storage/'; //视频文件存储的根路径
		//$rootPath = $this->config['PATH']['storage'];
		
		//如果获取成功
		if($missions)
		{
			//最终提交的任务
			$commitMissions = array();
			//判断是否符合任务规则
			foreach($missions as $mission)
			{
				while (!$rootPath = $this->server[$mission['serverid']]) {
					$this->getServerInfo();
				}
				$mission['srcpath'] = $rootPath . $mission['filepath'];
				if(file_exists($mission['srcpath']))
				{
					$this->printline('加入任务，ID：'.$mission['rid'].' 路径：'.$mission['srcpath']);
					$info = $this->getVideoInfo($mission['srcpath']); //由ffmpeg组件来分析视频基本信息

					$commitMissions[$mission['rid']] = array('size' => filesize($mission['srcpath']),
					'path' => $mission['srcpath'],
					'codec'=> $info['codec'],
					'trytime' => intval($mission['retry']),
					'duration' => $info['duration'],
					'guid' => array_shift(explode('.', basename($mission['srcpath'])))); //文件的基本名，扩展名之外的
					$this->commitMission($mission['rid'], 2);
				}
				else
				{
					$this->printline('任务[' . $mission['rid'] . ']文件不存在：'.$mission['srcpath']);
					$this->commitMission($mission['rid'], 3, intval($this->config['CONVERT']['try_time']));
				}
			}

			return $commitMissions;
		}
		else
		{
			$this->printline('没有新增任务', false);
			return array();
		}
	}

	/**
    * 更新任务状态
    * 
    * @param string $mission
    * @param string $flag
    */
	protected function commitMission($mission,$flag,$trytime = 0)
	{
		$prefix = (2 != $flag) ? ', retry = retry + 1' : NULL;
		$this->query("UPDATE cms_resource SET videostate = '{$flag}' {$prefix} WHERE rid='$mission' AND ifvideo=1", false);
	}

	protected function readResult($id,$mission,$result)
	{
		$pos = strpos($result,'##==##');
		if(false !== $pos)
		{
			$input = trim(substr($result,$pos + 7));
			parse_str($input,$out);

			if(!empty($out['pic']))
			{
				$pathinfo = pathinfo($mission['path']);
				$targetPath = $pathinfo['dirname'];
				
				$sourceVideo = $this->config['PATH']['convert_tmp'] . '/' . $mission['guid'] . '.flv';
				$sourcePic = $this->config['PATH']['convert_tmp'] . '/' . $mission['guid'] . '.jpg';
				if (!file_exists($sourceVideo) || !file_exists($sourcePic))
				{
					$this->printline('任务' . $id . '失败：无法获取转码后的截图或视频文件');
					return 3;
				}

				//转移视频文件
				$this->printline('任务' . $id . '开始生成缩略图', false);
				$this->resizeImage($sourcePic,
					array(
						array('width' => 0,'height' => 0,'file' => $targetPath . '/' . $mission['guid'] . '_1.jpg'),
						array('width' => 120,'height' => 90,'file' => $targetPath . '/' . $mission['guid'] . '_2.jpg'),
						array('width' => 85,'height' => 64,'file' => $targetPath . '/' . $mission['guid'] . '_3.jpg')
					)
				);
				//copy($this->config['PATH']['convert_tmp'].'/'.$mission['guid'].'.jpg', $targetPath . '/' . $mission['guid'] . '.jpg');
				$this->printline('转移任务['.$id . ']视频文件：' . $targetPath . '/' . $mission['guid'] . '.flv');
				copy($sourceVideo, $targetPath . '/' . $mission['guid'] . '.flv');
				sleep(3); //休息3秒钟
				//删除临时文件
				unlink($sourcePic);
				unlink($sourceVideo);		
				//$this->printline('视频路径:'.$targetPath . '/' . $mission['guid'] . '.flv');
				//$this->printline('图片路径:'.$targetPath . '/' . $mission['guid'] . '.jpg');
				$this->finishMission($id, filesize($targetPath . '/' . $mission['guid'] . '.flv'), $out['vTime']);
				return 1;
			}
			else
			{
				$this->printline('截图错误:'.$id);
				$this->printline($result);
				return 3;
			}
		}
		else
		{
			$this->printline('结果获取错误:'.$id);
			$this->printline($result);
			return 3;
		}
	}

	protected function finishMission($id, $size, $time)
	{
		$this->query("UPDATE cms_resource SET ifconvert = 1, videotime=$time WHERE rid='$id'"); //通知转码已经完成
		$this->printline('任务' . $id . '操作完毕。');
	}

	public function run()
	{
		while(true)
		{
			$missions = $this->getMissions();

			//打开相关进程
			foreach($missions as $id => $mission)
			{
				$args = '';
				$this->printline('打开任务'.$id.'，路径:'.$mission['path']);

				//打开一个进程
				$cmd =
				str_replace(array('%C','%s','%T','%d','%t'),
				array($mission['codec'],
				$mission['path'],
				$this->config['PATH']['convert_tmp'],
				$mission['guid'],
				999999),
				$this->config['CMD']['convert']);

				//echo $cmd;

				$process = proc_open($cmd,
				array
				(
				0 => array("pipe", "r"),
				1 => array("pipe", "w"),
				2 => array("file", $this->config['PATH']['error'], "w")
				), $pipes, $this->config['PATH']['app'], NULL);

				if(is_resource($process))
				{
					$mission['handle'] = $process;
					$mission['pipes'] = $pipes;
					$mission['time'] = 0;

					if(isset($this->currentMissions[$id]))
					{
						$this->printline('任务重复' . $id . ':' . $mission['path']);
					}

					$this->currentMissions[$id] = $mission;
					stream_set_blocking($pipes[1],0);
					$this->printline('执行任务' . $id);
				}
				else
				{
					//记录错误
					$this->printline('任务错误'.$id.':'.$mission['path']);
					$this->commitMission($id,3,$mission['trytime']);
				}
			}
			unset($mission);
			reset($this->currentMissions);
			$this->streams = array();

			//获取所有流
			foreach($this->currentMissions as $key => $mission)
			{
				$this->streams[$key] = $mission['pipes'][1];
			}

			//检查进程返回情况
			if($threadCount = count($this->currentMissions))
			{
				$this->printline('检测当前任务状态,共有'.$threadCount.'条任务正在处理');
				//只读流
				$reads = $this->streams;

				foreach($reads as $id => $read)
				{
					$this->printline('任务'.$id.'执行了:'.$this->currentMissions[$id]['time'].'秒');
					//如果超时,终止进程
					if($this->currentMissions[$id]['time'] > $this->config['CONVERT']['time_limit'])
					{
						$this->printline('任务超时'.$id.':'.$this->currentMissions[$id]['path']);
						$this->commitMission($id,4,$this->currentMissions[$id]['trytime']);
						proc_terminate($this->currentMissions[$id]['handle']);
						unset($this->streams[$id]);
						unset($this->currentMissions[$id]);
						unset($reads[$id]);
					}
					else
					{
						$this->currentMissions[$id]['time'] += $this->config['CONVERT']['schedule_time'];
					}
				}

				stream_select($reads, $w = null, $e = null, 0);

				foreach($reads as  $read)
				{
					$id = array_search($read,$this->streams);
					$result = stream_get_contents($this->currentMissions[$id]['pipes'][1]);

					if(feof($read))
					{
						fclose($this->currentMissions[$id]['pipes'][1]);
						$returnValue = proc_close($this->currentMissions[$id]['handle']);
						$flag = $this->readResult($id,$this->currentMissions[$id],$result);
						$this->printline('任务完成'.$id.':'.$this->currentMissions[$id]['path']);
						$this->commitMission($id,$flag,$this->currentMissions[$id]['trytime']);
						unset($this->streams[$id]);
						unset($this->currentMissions[$id]);
					}
				}
			}

			$this->currentThreadsNum = count($this->currentMissions);
			sleep($this->config['CONVERT']['schedule_time']);
		}
	}

	function __destruct()
	{
		fclose($this->handle);
		mysql_close($this->dbLink);
	}
}

if(!defined('INCLUDE'))
{
	$task = new VideoConvertTask(dirname(__FILE__).'/config.ini');
	$task->run();
}
?>
